﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using NetFrameworkExtensions;
using NetFrameworkExtensions.Reflection;
using System.Text.RegularExpressions;
using SoftwareEngineering.DataAnnotations;

namespace SoftwareEngineering.Modeling.DataModeling
{
  #region [ Base Models : DataModelBase ]
  [Serializable]
  public abstract partial class DataModelBase : ModelBase
  {
    public const string CLRNameRegex = @"^[a-zA-Z_][a-zA-Z_\d]*$";
    public const string CLRNamespaceRegex = @"^([a-zA-Z_][a-zA-Z_\d]*)(\.([a-zA-Z_][a-zA-Z_\d]*))*$";

    [Required]
    public virtual DataModel DataModel
    {
      get
      {
        return GetAncestorsAndSelf().OfType<DataModel>().FirstOrDefault();
      }
    }
  }

  [Serializable]
  public abstract partial class NamedDataModelBase : DataModelBase, IKeyedModel
  {
    [Required]
    [RegularExpression(CLRNameRegex)]
    public virtual string Name { get; set; }

    public override string ToString()
    {
      return string.Format("[{0} : {1}]", GetType().Name, Name);
    }

    string IKeyedModel.Key
    {
      get { return Name; }
    }
  }
  #endregion

  #region [ Main Models : DataModel, HierarchyType, EntityType, PropertyBase, Property, NavigationProperty ]
  public enum DataModelInitType { None, DropAlways, DropIfModified }

  [Serializable]
  [DebuggerDisplay("DataModel : {Name}")]
  public partial class DataModel : NamedDataModelBase
  {
    public DataModel()
    {
      EntityTypes        = new List<EntityType>();
      DataInitialization = new List<EntitySetInitCommand>();
    }

    [Required]
    [RegularExpression(CLRNamespaceRegex)]
    public virtual string Namespace { get; set; }

    public virtual DataModelInitType InitType { get; set; }

    [ContentProperty]
    [Required]
    public virtual IList<EntityType> EntityTypes { get; set; }
    
    [ContentProperty]
    [Required]
    public virtual IList<EntitySetInitCommand> DataInitialization { get; set; }

    protected override IEnumerable<ValidationResult> ValidateOverride(ValidationContext context)
    {
      foreach (var error in base.ValidateOverride(context))
        yield return error;

      var duplicatedEntity = EntityTypes.GetDuplicates(e => e.Name).FirstOrDefault();
      if (duplicatedEntity != null)
        yield return new ModelValidationResult("Duplicated EntityType name: " + duplicatedEntity.Name, Seq.Build("EntityTypes"), context);

      duplicatedEntity = EntityTypes.Where(e => e.BaseEntityTypeRef == null || e.RootEntityType.HierarchyType != HierarchyType.TablePerHierarchy).GetDuplicates(e => e.EntitySetName).FirstOrDefault();
      if (duplicatedEntity != null)
        yield return new ModelValidationResult("Duplicated EntityType EntitySetName: " + duplicatedEntity.EntitySetName, Seq.Build("EntityTypes"), context);

      var duplicatedCommand = DataInitialization.Where(e => e.Key != null).GetDuplicates(e => e.Key).FirstOrDefault();
      if (duplicatedCommand != null)
        yield return new ModelValidationResult("Duplicated Initialization Command key: " + duplicatedCommand.Key, Seq.Build("EntityTypes"), context);
    }
  }

  public enum HierarchyType { TablePerHierarchy, TablePerType, TablePerConcreteType }

  [Serializable]
  [DebuggerDisplay("EntityType : {Name}")]
  [System.Windows.Markup.ContentProperty("Properties")]
  public partial class EntityType : NamedDataModelBase, IComparable<EntityType>
  {
    public EntityType()
    {
      Properties = new List<PropertyBase>();
    }

    public virtual bool IsAbstract { get; set; }

    public virtual HierarchyType HierarchyType { get; set; }

    //[Required]
    //[RegularExpression(CLRNameRegex)]
    //public virtual string ToTable { get; set; }

    [Required]
    [RegularExpression(CLRNameRegex)]
    public virtual string EntitySetName { get; set; }

    public virtual string BaseEntityType { get; set; }
    public virtual EntityType BaseEntityTypeRef
    {
      get
      {
        if (string.IsNullOrEmpty(BaseEntityType)) return null;
        return default(EntityType).GetProtected(() => DataModel.EntityTypes.SingleOrDefault(e => e.Name == BaseEntityType));
      }
    }
    public virtual IEnumerable<EntityType> AllBaseEntityTypes { get { return this.Unfold(e => e.BaseEntityTypeRef, e => e == null); } }
    public virtual EntityType RootEntityType { get { return AllBaseEntityTypes.Last(); } }
    public virtual IEnumerable<EntityType> DirectDerivedTypes
    {
      get
      {
        return DataModel.EntityTypes.Where(e => e.BaseEntityTypeRef == this);
      }
    }
    public virtual IEnumerable<EntityType> DerivedTypes
    {
      get
      {
        return this.DepthFirstSearch(e => e.DirectDerivedTypes, false);
      }
    }

    [ContentProperty]
    [Required]
    public virtual IList<PropertyBase> Properties { get; set; }
    public virtual IEnumerable<PropertyBase> AllProperties { get { return AllBaseEntityTypes.SelectMany(e => e.Properties); } }
    public virtual IEnumerable<Property> PlainProperties { get { return Properties.OfType<Property>(); } }
    public virtual IEnumerable<ReferenceBase> References { get { return Properties.OfType<ReferenceBase>(); } }
    public virtual IEnumerable<Property> AllPlainProperties { get { return AllBaseEntityTypes.SelectMany(e => e.PlainProperties); } }
    public virtual IEnumerable<ReferenceBase> AllReferences { get { return AllBaseEntityTypes.SelectMany(e => e.References); } }

    public virtual IEnumerable<Tup<IEnumerable<ReferenceBase>, Property>> ConformKey()
    {
      foreach (var property in AllPlainProperties.Where(p => p.IsKey))
        yield return Tup.Create(Enumerable.Empty<ReferenceBase>(), property);
      foreach (var reference in AllReferences.Where(p => p.IsKey))
        foreach (var keyElement in reference.ConformKey())
          yield return keyElement;
    }

    public int CompareTo(EntityType other)
    {
      return Name.CompareTo(other.Name);
    }

    protected override IEnumerable<ValidationResult> ValidateOverride(ValidationContext context)
    {
      foreach (var error in base.ValidateOverride(context))
        yield return error;

      if (this.CyclesBack(e => e.BaseEntityTypeRef.SingletonNonNull()))
        yield return new ModelValidationResult("Cyclic entity type inheritance", Seq.Build("BaseEntityType"), context);

      var duplicatedProperty = Properties.GetDuplicates(e => e.Name).FirstOrDefault();
      if (duplicatedProperty != null)
        yield return new ModelValidationResult("Duplicated Property name: " + duplicatedProperty.Name, Seq.Build("Properties"), context);

      if (BaseEntityTypeRef != null && Properties.Where(p => p.IsKey).Any())
        yield return new ModelValidationResult("Derived type cannot have a key of its own", Seq.Build("BaseEntityType"), context);
    }
  }

  [Serializable]
  [System.Windows.Markup.ContentProperty("Configurations")]
  public abstract partial class PropertyBase : NamedDataModelBase, IComparable<PropertyBase>
  {
    public virtual EntityType DeclaringEntity { get { return GetParentAs<EntityType>(); } }

    public virtual bool IsKey { get; set; }

    bool _IsRequired;
    public virtual bool IsRequired
    {
      get { return IsKey || _IsRequired; }
      set { _IsRequired = value; }
    }

    public virtual bool AllowNull
    {
      get { return !IsRequired; }
    }

    public int CompareTo(PropertyBase other)
    {
      var comp = DeclaringEntity.CompareTo(other.DeclaringEntity);
      if (comp == 0) comp = Name.CompareTo(other.Name);
      return comp;
    }
  }

  [Serializable]
  [DebuggerDisplay("Property : {Name}")]
  [System.Windows.Markup.ContentProperty("Configurations")]
  public partial class Property : PropertyBase
  {
    public Property()
    {
      Configurations = new List<PropertyConfigurationBase>();
    }

    [Required]
    public virtual Type Type { get; set; }

    [ContentProperty]
    public virtual IList<PropertyConfigurationBase> Configurations { get; set; }
    public virtual bool HasConfig<TConf>() where TConf : PropertyConfigurationBase
    {
      return Configurations.OfType<TConf>().Any();
    }
    public virtual TConf GetConfig<TConf>() where TConf : PropertyConfigurationBase
    {
      return Configurations.OfType<TConf>().First();
    }
    public virtual bool TryGetConfig<TConf>(out TConf config) where TConf : PropertyConfigurationBase
    {
      config = Configurations.OfType<TConf>().FirstOrDefault();
      return config != null;
    }
  }

  [Serializable]
  public abstract partial class ReferenceBase : PropertyBase
  {
    public ReferenceBase()
    {
    }

    [Required]
    [RegularExpression(CLRNameRegex)]
    public virtual string EntityType { get; set; }

    [Required]
    public virtual EntityType EntityTypeRef
    {
      get
      {
        return default(EntityType).GetProtected(() => DataModel.EntityTypes.SingleOr(e => e.Name == EntityType, (EntityType)null));
      }
    }

    public virtual bool IsMultiple { get; set; }

    public virtual bool IsDependent { get; set; }

    public abstract bool OppositeReferenceIsMultiple { get; }

    public abstract bool OppositeReferenceIsDependent { get; }

    public abstract bool OppositeReferenceIsRequired { get; }

    public abstract bool MustBeDeclared { get; }

    public virtual IEnumerable<Tup<IEnumerable<ReferenceBase>, Property>> ConformKey()
    {
      foreach (var keyElement in EntityTypeRef.ConformKey())
        yield return Tup.Create(Seq.Build(this, keyElement.Item1).ToArray().AsEnumerable(), keyElement.Item2);
    }

    protected override IEnumerable<ValidationResult> ValidateOverride(ValidationContext context)
    {
      foreach (var error in base.ValidateOverride(context))
        yield return error;

      if (!IsMultiple && !OppositeReferenceIsMultiple &&
        IsDependent && OppositeReferenceIsDependent)
        yield return new ModelValidationResult("OneToOne references cannot be both dependent", Seq.Build("IsDependent"), context);

      if (IsKey && IsMultiple)
        yield return new ModelValidationResult("A multi-reference cannot be a key", Seq.Build("IsMultiple"), context);
    }
  }

  [Serializable]
  [DebuggerDisplay("Reference : {Name}")]
  public partial class Reference : ReferenceBase
  {
    public Reference()
    {
    }

    [Required]
    [RegularExpression(CLRNameRegex)]
    public virtual string OppositeReference { get; set; }

    [Required]
    public virtual Reference OppositeReferenceRef
    {
      get
      {
        return default(Reference).GetProtected(() => EntityTypeRef.References.OfType<Reference>().SingleOrDefault(r => r.Name == OppositeReference));
      }
    }

    public override bool OppositeReferenceIsDependent
    {
	    get
      {
        return OppositeReferenceRef != null && OppositeReferenceRef.IsDependent;
      }
    }

    public override bool OppositeReferenceIsMultiple
    {
      get
      {
        return OppositeReferenceRef != null && OppositeReferenceRef.IsMultiple;
      }
    }

    public override bool OppositeReferenceIsRequired
    {
      get
      {
        return OppositeReferenceRef != null && OppositeReferenceRef.IsRequired;
      }
    }

    public override bool MustBeDeclared 
    {
      get 
      {
        int comp = DeclaringEntity.Name.CompareTo(OppositeReferenceRef.DeclaringEntity.Name);
        if (comp == 0) comp = Name.CompareTo(OppositeReferenceRef.Name);
        if (comp < 0) return true;
        return false;
      }
    }

    protected override IEnumerable<ValidationResult> ValidateOverride(ValidationContext context)
    {
      foreach (var error in base.ValidateOverride(context))
        yield return error;

      if (OppositeReferenceRef != null)
      {
        if (OppositeReferenceRef.OppositeReferenceRef != this)
          yield return new ModelValidationResult("Opposite references must point each other", Seq.Build("OppositeReferenceRef"), context);
      }
    }
  }

  [Serializable]
  [DebuggerDisplay("UnidirectionalReference : {Name}")]
  public partial class UnidirectionalReference : ReferenceBase
  {
    public UnidirectionalReference()
    {
    }

    public bool OppositeIsDependent { get; set; }

    public bool OppositeIsMultiple { get; set; }

    public bool OppositeIsRequired { get; set; }

    public override bool OppositeReferenceIsDependent { get { return OppositeIsDependent; } }

    public override bool OppositeReferenceIsMultiple { get { return OppositeIsMultiple; } }

    public override bool OppositeReferenceIsRequired { get { return OppositeIsMultiple; } }

    public override bool MustBeDeclared { get { return true; } }
  }
  #endregion

  #region [ Property Configurations ]
  [Serializable]
  public abstract partial class PropertyConfigurationBase : DataModelBase
  {
    public Property DeclaringProperty { get { return GetParentAs<Property>(); } }
    public virtual bool IsEntitySpecific { get { return false; } }
  }

  [Serializable]
  [DebuggerDisplay("[HasColumnName = {ColumnName}]")]
  [System.Windows.Markup.ContentProperty("ColumnName")]
  public partial class HasColumnName : PropertyConfigurationBase
  {
    [RegularExpression(CLRNameRegex)]
    public string ColumnName { get; set; }

    public override string ToString()
    {
      return string.Format("[HasColumnName : {0}]", ColumnName);
    }
  }

  [Serializable]
  [DebuggerDisplay("[HasColumnOrder = {Order}]")]
  [System.Windows.Markup.ContentProperty("Order")]
  public partial class HasColumnOrder : PropertyConfigurationBase
  {
    public int Order { get; set; }

    public override string ToString()
    {
      return string.Format("[HasColumnOrder : {0}]", Order);
    }
  }

  [Serializable]
  [DebuggerDisplay("[HasColumnType = {Type}]")]
  [System.Windows.Markup.ContentProperty("Type")]
  public partial class HasColumnType : PropertyConfigurationBase
  {
    [Required]
    public string Type { get; set; }

    public override string ToString()
    {
      return string.Format("[HasColumnType : {0}]", Type);
    }
  }

  public enum DbGenOption { None, Identity, Computed }

  [Serializable]
  [DebuggerDisplay("[HasDatabaseGeneratedOption = {Option}]")]
  [System.Windows.Markup.ContentProperty("Option")]
  public partial class HasDatabaseGeneratedOption : PropertyConfigurationBase
  {
    public DbGenOption Option { get; set; }

    public override string ToString()
    {
      return string.Format("[HasDatabaseGeneratedOption : {0}]", Option);
    }
  }

  [Serializable]
  [DebuggerDisplay("[HasMaxLength = {Length}]")]
  [System.Windows.Markup.ContentProperty("Length")]
  public partial class HasMaxLength : PropertyConfigurationBase
  {
    public virtual int Length { get; set; }

    public override string ToString()
    {
      return string.Format("[HasMaxLength : {0}]", Length);
    }
  }

  [Serializable]
  [DebuggerDisplay("[HasPrecision = {Value}]")]
  [System.Windows.Markup.ContentProperty("Value")]
  public partial class HasPrecision : PropertyConfigurationBase
  {
    public virtual byte Value { get; set; }

    public virtual byte Scale { get; set; }

    public override string ToString()
    {
      return string.Format("[HasPrecision : {0}, {1}]", Value, Scale);
    }
  }

  [Serializable]
  [DebuggerDisplay("[IsConcurrencyToken]")]
  public partial class IsConcurrencyToken : PropertyConfigurationBase
  {
  }

  [Serializable]
  [DebuggerDisplay("[IsFixedLength]")]
  public partial class IsFixedLength : PropertyConfigurationBase
  {
  }

  [Serializable]
  [DebuggerDisplay("[IsMaxLength]")]
  public partial class IsMaxLength : PropertyConfigurationBase
  {
  }

  [Serializable]
  [DebuggerDisplay("[IsRowVersion]")]
  public partial class IsRowVersion : PropertyConfigurationBase
  {
  }

  [Serializable]
  [DebuggerDisplay("[IsVariableLength]")]
  public partial class IsVariableLength : PropertyConfigurationBase
  {
  }

  [Serializable]
  [DebuggerDisplay("[IsUnicode = {Length}]")]
  public partial class IsUnicode : PropertyConfigurationBase
  {
  }
  #endregion

  #region [ Data Initialization Models : EntitySetInitCommand, EntitySetSpecificCommand, Insert, Setter, SetterValue ]
  [Serializable]
  public abstract partial class EntitySetInitCommand : DataModelBase, IKeyedModel
  {
    public virtual string Key { get; set; }

    public override string ToString()
    {
      return string.Format("[{0} : {1}]", GetType().Name, Key);
    }
  }

  [Serializable]
  public abstract partial class EntitySetSpecificCommand : EntitySetInitCommand
  {
    [Required]
    [RegularExpression(CLRNameRegex)]
    public virtual string EntitySet { get; set; }
    [Required]
    public virtual EntityType EntitySetRef
    {
      get
      {
        if (string.IsNullOrEmpty(EntitySet)) return null;
        return default(EntityType).GetProtected(() => DataModel.EntityTypes.SingleOrDefault(e => (e.EntitySetName == null && e.Name == EntitySet || e.EntitySetName == EntitySet)));
      }
    }
  }

  [Serializable]
  [System.Windows.Markup.ContentProperty("Setters")]
  [DebuggerDisplay("Insert on {DataSet} / {Key}")]
  public partial class Insert : EntitySetSpecificCommand
  {
    public Insert()
    {
      Setters = new List<Setter>();
    }

    [Required]
    [ContentProperty]
    public virtual IList<Setter> Setters { get; set; }

    protected override IEnumerable<ValidationResult> ValidateOverride(ValidationContext context)
    {
      foreach (var error in base.ValidateOverride(context))
        yield return error;

      var duplicatedSetter = Setters.GetDuplicates(e => e.Property).FirstOrDefault();
      if (duplicatedSetter != null)
        yield return new ModelValidationResult("Duplicated Setter property: " + duplicatedSetter.Property, Seq.Build("Properties"), context);
    }
  }

  [Serializable]
  [System.Windows.Markup.ContentProperty("Values")]
  [DebuggerDisplay("Setter of {Property} = {Value}")]
  public partial class Setter : DataModelBase, IKeyedModel
  {
    public Setter()
    {
      Values = new List<SetterValue>();
    }

    [Required]
    [RegularExpression(CLRNameRegex)]
    public virtual string Property { get; set; }
    [Required]
    public virtual PropertyBase PropertyRef
    {
      get
      {
        if (string.IsNullOrEmpty(Property)) return null;
        return default(PropertyBase).GetProtected(() => GetClosest<EntitySetSpecificCommand>().EntitySetRef.AllProperties.SingleOrDefault(e => e.Name == Property));
      }
    }

    public virtual string Value { get; set; }
    public string CSharpLiteral
    {
      get
      {
        return GetCSharpLiteral(Value);
      }
    }
    public string GetCSharpLiteral(string value)
    {
      if (PropertyRef is Property)
      {
        var type = ((Property)PropertyRef).Type;
        if (value == null)
          if (!type.IsValueType) return "null";
          else return string.Format("default({0})", type.ToCommonString());
        
        if (type == typeof(string))
          return string.Format(@"@""{0}""", value.AsVerbatim());
        if (type == typeof(bool) || type == typeof(bool?))
          return  value.ToLower() == "true" ? "true" : (value.ToLower() == "false" ? "false" : string.Format("[format exception for bool: {0}]", value));
        if (type == typeof(byte) || type == typeof(byte?))
          return "(byte)" + byte.Parse(value).ToString();
        if (type == typeof(short) || type == typeof(short?))
          return "(short)" + short.Parse(value).ToString();
        if (type == typeof(int) || type == typeof(int?))
          return int.Parse(value).ToString();
        if (type == typeof(int) || type == typeof(int?))
          return int.Parse(value).ToString();
        if (type == typeof(long) || type == typeof(long?))
          return long.Parse(value).ToString() + "l";
        if (type == typeof(double) || type == typeof(double?))
          return double.Parse(value).ToString() + "d";
        if (type == typeof(float) || type == typeof(float?))
          return float.Parse(value).ToString() + "f";
        if (type == typeof(decimal) || type == typeof(decimal?))
          return decimal.Parse(value).ToString() + "m";
        if (type == typeof(Guid) || type == typeof(Guid?))
        {
          if (value.ToLower() == "empty") return "Guid.Empty";
          if (value.ToLower() == "newguid") return "Guid.NewGuid()";
          return string.Format(@"new Guid(@""{0}"")", Guid.Parse(value).ToString("D"));
        }
        if (type == typeof(DateTime) || type == typeof(DateTime?))
        {
          if (value.ToLower() == "now") return "DateTime.Now";
          if (value.ToLower() == "today") return "DateTime.Today";
          if (value.ToLower() == "utcnow") return "DateTime.UtcNow";
          return @"DateTime.Parse(@""" + value.AsVerbatim() + @""")";
        }
        if (type == typeof(TimeSpan) || type == typeof(TimeSpan?))
        {
          return @"TimeSpan.Parse(@""" + value.AsVerbatim() + @""")";
        }

        throw new FormatException(string.Format("[unknown type literal: {0}]", type.FullName));
      }
      throw new FormatException(string.Format("[property is not literal: {0}]", PropertyRef.Name));
    }

    [ContentProperty]
    public virtual List<SetterValue> Values { get; set; }

    public override string ToString()
    {
      return string.Format("[Setter : {0}]", Property);
    }

    string IKeyedModel.Key
    {
      get { return Property; }
    }
  }

  [Serializable]
  [DebuggerDisplay("SetterValue with {Value}")]
  public partial class SetterValue : DataModelBase
  {
    [Required]
    public string Value { get; set; }

    public string CSharpLiteral
    {
      get
      {
        return GetClosest<Setter>().GetCSharpLiteral(Value);
      }
    }

    public override string ToString()
    {
      return string.Format("[SetterValue : {0}]", Value);
    }
  }
  #endregion

  public static partial class DataModelingExtensions
  {
    public static string KeyName(this Tup<IEnumerable<ReferenceBase>, Property> keyPath)
    {
      if (!keyPath.Item1.Any()) return keyPath.Item2.Name;
      var result = keyPath.Item2.Name == keyPath.Item2.DeclaringEntity.Name + "Id" ? "Id" : "_" + keyPath.Item2.Name;
      result = keyPath.Item1.ToStringList("_", r => r.Name) + result;
      return result;
    }
    public static string KeyType(this Tup<IEnumerable<ReferenceBase>, Property> keyPath)
    {
      var firstRef = keyPath.Item1.FirstOrDefault();
      bool allowNull = firstRef == null ? false : firstRef.AllowNull;
      var result = keyPath.Item2.Type.ToCommonString(allowNull);
      return result;
    }
  }
}
